/**
 * Copyright (c) 2008 Andrew Rapp. All rights reserved.
 *  
 * This file is part of XBee-API.
 *  
 * XBee-API is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *  
 * XBee-API is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *  
 * You should have received a copy of the GNU General Public License
 * along with XBee-API.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.rapplogic.xbee;

import org.apache.log4j.Level;
import javax.comm.CommPortIdentifier;
import javax.comm.PortInUseException;
import javax.comm.SerialPort;
import javax.comm.SerialPortEvent;
import javax.comm.SerialPortEventListener;
import javax.comm.UnsupportedCommOperationException;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.TooManyListenersException;

import org.apache.log4j.Logger;
import javax.comm.NoSuchPortException;

/** 
 * This class encapsulates a RXTX serial port, providing access to input/output streams,
 * and notifying the subclass of new data events via the handleSerialData method.
 * 
 * @author andrew
 * 
 */
public abstract class RxTxSerialComm implements SerialPortEventListener {

    private final static Logger log = Logger.getLogger(RxTxSerialComm.class);
    private InputStream inputStream;
    private OutputStream outputStream;
    private SerialPort serialPort;

    public RxTxSerialComm() {
        log.setLevel(Level.WARN);

    }

    protected void openSerialPort(String port, int baudRate) throws PortInUseException, UnsupportedCommOperationException, TooManyListenersException, IOException {
        this.openSerialPort(port, "XBee", 0, baudRate, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE, SerialPort.FLOWCONTROL_NONE);
    }

    protected void openSerialPort(String port, String appName, int timeout, int baudRate) throws PortInUseException, UnsupportedCommOperationException, TooManyListenersException, IOException {
        this.openSerialPort(port, appName, timeout, baudRate, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE, SerialPort.FLOWCONTROL_NONE);
    }

    protected void openSerialPort(String port, String appName, int timeout, int baudRate, int dataBits, int stopBits, int parity, int flowControl) throws PortInUseException, UnsupportedCommOperationException, TooManyListenersException, IOException {
        // Apparently you can't query for a specific port, but instead must iterate
        //System.out.println("RxTxSerialComm.openSerialPort()");
        Enumeration portList = CommPortIdentifier.getPortIdentifiers();

        CommPortIdentifier portId = null;

        boolean found = false;
        try {
            //new
            portId = CommPortIdentifier.getPortIdentifier(port);
            found = true;
        } catch (Exception ex) {
            //System.out.println("RxTxSerialComm.openSerialPort() exception");
            ex.printStackTrace();
        } finally {
            //System.out.println("RxTxSerialComm.openSerialPort() port found");

        }
        //end new
        /*while (portList.hasMoreElements()) {

        portId = (CommPortIdentifier) portList.nextElement();

        if (portId.getPortType() == CommPortIdentifier.PORT_SERIAL) {

        log.debug("Found port: " + portId.getName());

        if (portId.getName().equals(port)) {
        log.debug("Using Port: " + portId.getName());
        found = true;
        break;
        }
        }
        }*/
        //System.out.println("RxTxSerialComm.openSerialPort() 2");

        if (!found) {
            System.out.println("RxTxSerialComm.openSerialPort() 2.1");
            throw new RuntimeException("Could not find port: " + port);
        }
        //System.out.println("RxTxSerialComm.openSerialPort() 2.5");

        serialPort = (SerialPort) portId.open(appName, timeout);
        //System.out.println("RxTxSerialComm.openSerialPort() 3");

        serialPort.setSerialPortParams(baudRate, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
        //System.out.println("RxTxSerialComm.openSerialPort() 4");
        serialPort.setFlowControlMode(SerialPort.FLOWCONTROL_NONE);
        //System.out.println("RxTxSerialComm.openSerialPort() 5");

        // activate the DATA_AVAILABLE notifier
        serialPort.notifyOnDataAvailable(true);
        //System.out.println("RxTxSerialComm.openSerialPort() 6");

        // activate the OUTPUT_BUFFER_EMPTY notifier
        //serialPort.notifyOnOutputEmpty(true);

        serialPort.addEventListener(this);
        //System.out.println("RxTxSerialComm.openSerialPort() 7");

        inputStream = serialPort.getInputStream();
        outputStream = serialPort.getOutputStream();
        //System.out.println("RxTxSerialComm.openSerialPort() 8");
    }

    /**
     * Shuts down RXTX
     */
    public void close() {
        try {
            serialPort.close();
        } catch (Exception e) {
        }
    }

    protected OutputStream getOutputStream() {
        return outputStream;
    }

    protected InputStream getInputStream() {
        return inputStream;
    }

    protected abstract void handleSerialData() throws IOException;

    public void serialEvent(SerialPortEvent event) {
        switch (event.getEventType()) {
            case SerialPortEvent.DATA_AVAILABLE:

                try {
                    if (inputStream.available() > 0) {
                        try {
                            log.debug("serialEvent: " + inputStream.available() + " bytes available");
                            handleSerialData();
                        } catch (Exception e) {
                            log.error("Error in handleSerialData method", e);
                        }
                    }
                } catch (IOException ex) {
                    // it's best not to throw the exception because the RXTX thread may not be prepared to handle
                    log.error("RXTX error in serialEvent method", ex);
                }
        }
    }
}